package handler

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/golang/protobuf/ptypes/empty"
	"github.com/olivere/elastic/v7"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"
	. "service/goods_srv/global"
	"service/goods_srv/model"
	"service/goods_srv/proto"
	"strconv"
	"strings"
)

// 实现商品服务（service层）的接口
// 接口定义文件——goods.proto

type GoodsServer struct {
	// 用以忽略接口内部实现逻辑，从而快速启动grpc服务测试
	//proto.UnimplementedGoodsServer
}

/*
//商品接口
GoodsList(context.Context, *GoodsFilterRequest) (*GoodsListResponse, error)
//现在用户提交订单有多个商品，你得批量查询商品的信息吧
BatchGetGoods(context.Context, *BatchGoodsIdInfo) (*GoodsListResponse, error)
CreateGoods(context.Context, *CreateGoodsInfo) (*GoodsInfoResponse, error)
DeleteGoods(context.Context, *DeleteGoodsInfo) (*emptypb.Empty, error)
UpdateGoods(context.Context, *CreateGoodsInfo) (*emptypb.Empty, error)
GetGoodsDetail(context.Context, *GoodInfoRequest) (*GoodsInfoResponse, error)
*/

// 创建商品
func (g *GoodsServer) CreateGoods(c context.Context, r *proto.CreateGoodsInfo) (*proto.GoodsInfoResponse, error) {
	// 这里没有看到图片文件是如何上传的。实际上在微服务中，普通的文件上传方式已经不再适用
	// 后续会使用阿里云的OSS服务来解决图片上传的问题
	var category *model.Category
	if result := DB.First(&category, r.CategoryId); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.InvalidArgument, "商品分类不存在")
	}

	var brand *model.Brands
	if result := DB.First(&brand, r.BrandId); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.InvalidArgument, "品牌不存在")
	}

	goods := model.Goods{
		Brands:          brand,
		BrandsID:        brand.ID,
		Category:        category,
		CategoryID:      category.ID,
		Name:            r.Name,
		GoodsSn:         r.GoodsSn,
		MarketPrice:     r.MarketPrice,
		ShopPrice:       r.ShopPrice,
		GoodsBrief:      r.GoodsBrief,
		ShipFree:        r.ShipFree,
		Images:          r.Images,
		DescImages:      r.DescImages,
		GoodsFrontImage: r.GoodsFrontImage,
		IsNew:           r.IsNew,
		IsHot:           r.IsHot,
		OnSale:          r.OnSale,
	}

	// 开启事务
	tx := DB.Begin()

	// 准备在创建商品后的钩子函数中设置库存
	tx = tx.Set("good_stock", r.Stocks)

	// 新建商品后会条用一个AfterCreate钩子，同步es操作将在这个钩子中完成
	result := tx.Save(&goods)
	if result.Error != nil {
		tx.Rollback()
		zap.S().Errorf("创建商品失败：%v", result.Error)
		return nil, status.Errorf(codes.Internal, "创建商品失败：%v", result.Error)
	}

	tx.Commit()

	return &proto.GoodsInfoResponse{
		Id: goods.ID,
	}, nil
}

func (g *GoodsServer) GoodsList(c context.Context, r *proto.GoodsFilterRequest) (*proto.GoodsListResponse, error) {
	// 接口需求：关键词搜索、查询新品、查询热门商品、通过价格区间筛选， 通过商品分类筛选
	// 请求参数多数都是可选的

	boolQuery := elastic.NewBoolQuery()
	var filterQuery []elastic.Query

	if r.KeyWords != "" {
		// 关键词搜索
		// 只有name字段搜索时需要计算score，其余字段都可用filter查询
		boolQuery.Must(elastic.NewMultiMatchQuery(r.KeyWords, "name", "goods_brief"))
	}
	if r.IsHot {
		filterQuery = append(filterQuery, elastic.NewTermQuery("is_hot", true))
	}
	if r.IsNew {
		filterQuery = append(filterQuery, elastic.NewTermQuery("is_new", true))
	}
	if r.IsTab {
		filterQuery = append(filterQuery, elastic.NewTermQuery("is_new", true))
	}

	if r.PriceMin > 0 || r.PriceMax > 0 {
		priceQuery := elastic.NewRangeQuery("shop_price")
		if r.PriceMin > 0 {
			priceQuery.Gte(r.PriceMin)
		}
		if r.PriceMax > 0 {
			priceQuery.Lte(r.PriceMax)
		}
		filterQuery = append(filterQuery, priceQuery)
	}

	if r.Brand > 0 {
		filterQuery = append(filterQuery, elastic.NewTermQuery("brands_id", r.Brand))
	}

	// 通过分类查询商品
	// subQuery是通过分类id找出
	var subQuery string
	if r.TopCategory > 0 {
		// 根据请求参数中的分类id找到该分类下的所有三级分类id
		category := new(model.Category)
		if result := DB.First(category, r.TopCategory); result.RowsAffected == 0 {
			return nil, status.Errorf(codes.NotFound, "商品分类不存在")
		}

		if category.Level == 1 {
			subQuery = fmt.Sprintf("select id from category where parent_category_id in (select id from category WHERE parent_category_id=%d)", r.TopCategory)
		} else if category.Level == 2 {
			subQuery = fmt.Sprintf("select id from category WHERE parent_category_id=%d", r.TopCategory)
		} else if category.Level == 3 {
			subQuery = fmt.Sprintf("select id from category WHERE id=%d", r.TopCategory)
		}

		var categorys []*model.Category
		DB.Raw(subQuery).Scan(&categorys)
		if len(categorys) != 0 {
			var ids []interface{}
			for _, category = range categorys {
				ids = append(ids, category.ID)
			}
			filterQuery = append(filterQuery, elastic.NewTermsQuery("category_id", ids...))
		}
	}

	if len(filterQuery) != 0 {
		boolQuery.Filter(filterQuery...)
	}
	from, size := PaginateForEs(int(r.Pages), int(r.PagePerNums))
	rsp, err := EsClient.Search().
		Index("ygshop_goods").
		//偏移量
		From(from).
		//返回数据的条数
		Size(size).
		Query(boolQuery).
		Do(context.Background())
	if err != nil {
		zap.S().Error("es查询失败：", err)
		return nil, status.Errorf(codes.Internal, "es查询失败: %v", err)
	}

	goodsListResponse := new(proto.GoodsListResponse)
	// 获取查询商品结果的总数
	goodsListResponse.Total = int32(rsp.TotalHits())

	var resIds []int32
	for _, val := range rsp.Hits.Hits {
		good := new(model.EsGoods)
		json.Unmarshal(val.Source, good)
		resIds = append(resIds, good.ID)
	}
	if len(resIds) == 0 {
		zap.S().Error("es查询：未搜索到符合条件的商品")
		return nil, status.Errorf(codes.NotFound, "未搜索到符合条件的商品")
	}
	zap.S().Debug("搜索结果：", resIds)

	var goods []*model.Goods
	var buffer []string
	for _, v := range resIds {
		buffer = append(buffer, strconv.FormatInt(int64(v), 10))
	}
	// field(id,3,12,8,7,10,17,9,11,59)
	orderStr := fmt.Sprintf("field(id,%s)", strings.Join(buffer, ","))
	// Find(&goods, resIds)：根据多个主键id查询多条商品记录
	if res := DB.Preload("Category").Preload("Brands").Order(orderStr).Find(&goods, resIds); res.Error != nil {
		zap.S().Error("mysql查询失败：", res.Error)
		return nil, status.Errorf(codes.Internal, "mysql查询失败：", res.Error)
	}
	for _, good := range goods {
		goodsInfoResponse := ModelToResponse(good)
		goodsListResponse.Data = append(goodsListResponse.Data, goodsInfoResponse)
	}
	return goodsListResponse, nil
}

// 批量查询商品信息
func (g *GoodsServer) BatchGetGoods(c context.Context, r *proto.BatchGoodsIdInfo) (*proto.GoodsListResponse, error) {
	goods := make([]*model.Goods, 0, len(r.Id))
	result := DB.Preload("Category").Preload("Brands").Where("id in ?", r.Id).Find(&goods)
	if result.Error != nil {
		return nil, status.Errorf(codes.NotFound, "某个商品id不存在")
	}

	goodsListResponse := new(proto.GoodsListResponse)
	goodsListResponse.Total = int32(result.RowsAffected)
	for _, v := range goods {
		goodsListResponse.Data = append(goodsListResponse.Data, ModelToResponse(v))
	}

	return goodsListResponse, nil
}

// 获取商品详情
func (g *GoodsServer) GetGoodsDetail(c context.Context, r *proto.GoodInfoRequest) (*proto.GoodsInfoResponse, error) {
	good := new(model.Goods)
	good.ID = r.Id
	result := DB.Preload("Category").Preload("Brands").Where(good).Find(&good)
	if result.Error != nil || result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "商品不存在")
	}
	return ModelToResponse(good), nil
}

// 删除商品
func (g *GoodsServer) DeleteGoods(c context.Context, r *proto.DeleteGoodsInfo) (*emptypb.Empty, error) {
	good := new(model.Goods)
	good.ID = r.Id

	result := DB.Where(good).First(good)
	if result.RowsAffected != 1 {
		return nil, status.Errorf(codes.NotFound, "商品不存在")
	}

	tx := DB.Begin()
	result = tx.Delete(&good)
	if result.Error != nil {
		tx.Rollback()
		return nil, status.Errorf(codes.Internal, result.Error.Error())
	}
	tx.Commit()
	return &empty.Empty{}, nil
}

// 更新商品
func (g *GoodsServer) UpdateGoods(c context.Context, r *proto.CreateGoodsInfo) (*emptypb.Empty, error) {
	good := new(model.Goods)
	good.ID = r.Id

	result := DB.Where(good).First(good)
	if result.RowsAffected != 1 {
		return nil, status.Errorf(codes.NotFound, "商品不存在")
	}

	category := new(model.Category)
	if result = DB.First(category, r.CategoryId); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "商品分类不存在")
	}

	brand := new(model.Brands)
	if result = DB.First(brand, r.BrandId); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "品牌不存在")
	}

	good.Brands = brand
	good.BrandsID = brand.ID
	good.Category = category
	good.CategoryID = category.ID
	good.Name = r.Name
	good.GoodsSn = r.GoodsSn
	good.MarketPrice = r.MarketPrice
	good.ShopPrice = r.ShopPrice
	good.GoodsBrief = r.GoodsBrief
	good.ShipFree = r.ShipFree
	good.Images = r.Images
	good.DescImages = r.DescImages
	good.GoodsFrontImage = r.GoodsFrontImage
	good.IsNew = r.IsNew
	good.IsHot = r.IsHot
	good.OnSale = r.OnSale

	// 请求参数中包含零值字段也要更新
	tx := DB.Begin()
	result = tx.Save(good)
	if result.Error != nil {
		tx.Rollback()
		return nil, status.Errorf(codes.Internal, result.Error.Error())
	}
	tx.Commit()
	return &empty.Empty{}, nil
}

// 更新商品的状态
func (g *GoodsServer) UpdateGoodsStatus(c context.Context, r *proto.CreateGoodsInfo) (*emptypb.Empty, error) {
	good := new(model.Goods)
	good.ID = r.Id

	result := DB.Where(good).First(good)
	if result.RowsAffected != 1 {
		return nil, status.Errorf(codes.NotFound, "商品不存在")
	}

	good.IsNew = r.IsNew
	good.IsHot = r.IsHot
	good.OnSale = r.OnSale

	// 更新选定字段
	tx := DB.Begin()
	result = tx.Select("is_new", "is_hot", "on_sale").Updates(good)
	if result.Error != nil {
		tx.Rollback()
		return nil, status.Errorf(codes.Internal, result.Error.Error())
	}
	tx.Commit()
	return &empty.Empty{}, nil
}

func ModelToResponse(goods *model.Goods) *proto.GoodsInfoResponse {
	return &proto.GoodsInfoResponse{
		Id:              goods.ID,
		CategoryId:      goods.CategoryID,
		Name:            goods.Name,
		GoodsSn:         goods.GoodsSn,
		ClickNum:        goods.ClickNum,
		SoldNum:         goods.SoldNum,
		FavNum:          goods.FavNum,
		MarketPrice:     goods.MarketPrice,
		ShopPrice:       goods.ShopPrice,
		GoodsBrief:      goods.GoodsBrief,
		ShipFree:        goods.ShipFree,
		GoodsFrontImage: goods.GoodsFrontImage,
		IsNew:           goods.IsNew,
		IsHot:           goods.IsHot,
		OnSale:          goods.OnSale,
		DescImages:      goods.DescImages,
		Images:          goods.Images,
		Category: &proto.CategoryBriefInfoResponse{
			Id:   goods.Category.ID,
			Name: goods.Category.Name,
		},
		Brand: &proto.BrandInfoResponse{
			Id:   goods.Brands.ID,
			Name: goods.Brands.Name,
			Logo: goods.Brands.Logo,
		},
	}
}

func PaginateForEs(page, pageSize int) (int, int) {
	if page == 0 {
		page = 1
	}

	switch {
	case pageSize > 100:
		pageSize = 100
	case pageSize <= 0:
		pageSize = 10
	}

	offset := (page - 1) * pageSize
	return offset, pageSize
}
